package com.hy.video.monitor.config;

import com.hy.video.monitor.security.auth.ExternalAuthenticationProvider;
import com.hy.video.monitor.security.dsl.ClientErrorLoggingConfigurer;
import com.hy.video.monitor.security.jwt.JwtFilter;
import com.hy.video.monitor.security.rolehierarchy.RoleHierarchyService;
import com.hy.video.monitor.util.Constants;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import java.util.*;

@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@Configuration
@Order(99)
@Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final SecurityProblemSupport problemSupport;
    private final JwtFilter jwtFilter;
    private final Environment environment;

    //@Lazy
    private final RoleHierarchyService roleHierarchyService;

//    private final SecurityProblemSupport problemSupport;
//    private final LDAPAuthService ldapAuthService;
//    private final JwtFilter jwtFilter;
//    private final Environment environment;
//    private final ObjectMapper objectMapper;
//    private final UserDetailsServiceImpl userDetailsServiceImpl;
//    private final UserDetailsPasswordServiceImpl userDetailsPasswordServiceImpl;
//    private final UserCacheService userCacheService;
//    private final JwtUtil jwtUtil;
//    private final LdapProperties ldapProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .httpBasic(AbstractHttpConfigurer::disable)
            .csrf(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
            .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 配置跨域
            .sessionManagement(sessionManagement -> sessionManagement
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 添加异常处理
            .exceptionHandling(exceptionHandling -> exceptionHandling
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport))
            // 安全表达式
            .authorizeRequests(authorizeRequests -> authorizeRequests
                /**
                 * 针对 ZLMediaServer的hook事件监听:放行"/index/hook/"
                 */
                .mvcMatchers("/", "/authorize/**").permitAll()
                /**
                 *  hasRole
                 *  hasAnyRole('ADMIN','USER') 书写不需要前缀，默认会加上
                 *  hasAuthority
                 *  hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER') 书写需要加前缀(或关系)
                 */
                //.antMatchers("/admin/**").hasRole("ADMIN")
                //.mvcMatchers("/api/users/by-email/{email}").hasRole("USER")
                // authentication：当前认证过的用户，就是说只有登录认证成功后的当前用户才有权访问: /api/users/{username}/**下的接口
                // 非常受欢迎的security表达式，这样可以让控制权限独立于业务
//                .mvcMatchers("/api/users/{username}/**").access("hasRole('" +
//                    Constants.AUTHORITY_ADMIN + "')  or @userValidationService.checkUsername(authentication, #username)")
                    .mvcMatchers("/admin/**").authenticated()
                    .mvcMatchers("/api/**").authenticated()// 已经被认证通过用户就可以访问/api/**下的接口
                // 越广泛适用的规则放到最后，以免其它规则失效。
                .anyRequest().denyAll())
            // LDAP认证源过滤器1
            //.addFilterBefore(new LDAPAuthorizationFilter(new AntPathRequestMatcher("/api/**")), UsernamePasswordAuthenticationFilter.class)
            // DAO认证源+jwt认证过滤器2
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
//            .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        ;
    }

    @Override
    public void configure(WebSecurity web) {
        web
            .ignoring()
            .antMatchers("/resources/**", "/static/**", "/public/**", "/h2-console/**",
                    "/index/**", "/doc.html", "/swagger-resources/**", "/v3/**", "/api/emit/**")
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /*
         * 希望使用内置的 LDAP 认证可以使用下面代码块代替自定义的 LDAPAuthenticationProvider
         * auth.ldapAuthentication()
         *             .userDnPatterns("uid={0}")
         *             .contextSource()
         *             .url(ldapProperties.getUrls()[0])
         *             .root(ldapProperties.getBase());
         */
        auth
            .authenticationProvider(new ExternalAuthenticationProvider());

//        auth
//            .userDetailsService(userDetailsServiceImpl) // 配置 AuthenticationManager 使用数据库 userService
//            .passwordEncoder(passwordEncoder()) // 配置密码升级工具
//            .userDetailsPasswordManager(userDetailsPasswordServiceImpl); // 配置密码自动升级服务
    }

    /**
     * 我们在 Spring Boot 中有几种其他方式配置 CORS
     * 参见 https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors
     * Mvc 的配置方式见 WebMvcConfig 中的代码
     * SpringSecurity中配置前端请求跨域：
     * @return CorsConfigurationSource
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许跨域访问的主机
        if (environment.acceptsProfiles(Profiles.of("dev"))) {
            // 开发环境放行“http://localhost:4001”访问实现跨域
            // 设置流媒体 hook 接口回调跨域
            configuration.setAllowedOrigins(Collections.singletonList("http://10.10.23.134:18080"));
            // 设置前端请求跨域
            configuration.setAllowedOrigins(Collections.singletonList("http://10.10.22.82:4001"));
        } else {
            // 生产环境放行“https://uaa.vmc.com”访问实现跨域
            configuration.setAllowedOrigins(Collections.singletonList("https://uaa.vmc.com"));
        }
        // 设置开放前端请求允许的方法实现跨域
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        // 设置开放前端请求允许的头实现跨域
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        // 设置开放服务器响应头实现跨域-用于响应，X-Authenticate:mfa,realm=<请求id>返回给前端，用于验二次证码请求携带
        configuration.addExposedHeader("X-Authenticate");// 跨域响应也头需要暴漏出去，否则不能响应该头属性给客户端
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public ClientErrorLoggingConfigurer clientErrorLogging() {
        return new ClientErrorLoggingConfigurer(new ArrayList<>());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 默认编码算法的 Id
        val idForEncode = "bcrypt";
        // 要支持的多种编码器
        val encoders = Map.of(
            idForEncode, new BCryptPasswordEncoder(),
            "SHA-1", new MessageDigestPasswordEncoder("SHA-1")
        );
        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }

//    @Bean
//    @Override
//    protected AuthenticationManager authenticationManager() throws Exception {
//        return super.authenticationManager();
//    }

    /**
     * ObjectMapper类的readValue API 是简单的入门起点：可以解析或反序列化json内容为java对象。
     * 反之，writeValue API可以序列化任何java对象为json字符串。
     * @return
     * @throws Exception
     */
//    @Bean
//    public RestAuthenticationFilter restAuthenticationFilter() throws Exception {
//        val filter = new RestAuthenticationFilter(objectMapper, userDetailsServiceImpl, userCacheService);
//        filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler(jwtUtil));
//        filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler(objectMapper));
//        filter.setAuthenticationManager(authenticationManager());
//        // 设置确定是否需要身份验证的"/authorize/login"
//        filter.setFilterProcessesUrl("/authorize/login");
//        return filter;
//    }

    /**
     * Spring Security 提供RoleHierarchyVoter,
     * 通过配置RoleHierarchy得到用户被分配的所有可到达的权限
     * @return
     */
    @ConditionalOnProperty(prefix = "mooc.security", name = "role-hierarchy-enabled", havingValue = "true")
    @Bean
    public RoleHierarchyImpl roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy(roleHierarchyService.getRoleHierarchyExpr());
        return roleHierarchy;
    }
}
